package com.qingzhuge.web.interceptor;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.qingzhuge.cache.CacheChannel;
import com.qingzhuge.common.HttpCode;
import com.qingzhuge.common.utils.WebUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;

import static com.qingzhuge.common.Constants.*;

/**
 * @author : zero.xiao
 * @description : 恶意请求拦截器
 * @modified :
 */
@EqualsAndHashCode(callSuper = true)
@Data
@Component
public class MaliciousRequestInterceptor extends Interceptor {
    private String requestCache = "request";
    /**
     * 非法请求时间
     */
    private String malicious = "malicious_time:";
    /**
     * 上次请求地址
     */
    private String lastRequestUrl = "last_url:";
    /**
     * 上次请求时间
     */
    private String lastRequestTime = "last_time:";

    @Autowired(required = false)
    private CacheChannel channel;
    /**
     * 拦截所有请求,否则拦截相同请求
     */
    private boolean allRequest = false;

    /**
     * 包含参数
     */
    private boolean containsParameter = true;

    /**
     * 允许的最小请求间隔
     */
    private int minRequestIntervalTime = 100;

    /**
     * 允许的最大恶意请求次数
     */
    private int maxMaliciousNum = 0;

    /**
     * 白名单
     */
    private List<String> whiteUrls = Lists.newArrayList();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String url = request.getServletPath();
        String unauthorized = "/unauthorized";
        String forbidden = "/forbidden";
        if (url.endsWith(unauthorized) || url.endsWith(forbidden) || isWhiteReq(url.toLowerCase())) {
            return super.preHandle(request, response, handler);
        }
        if (containsParameter) {
            url += JSON.toJSONString(WebUtil.getParameterMap(request));
        }
        Long userId = WebUtil.getSysUserId();
        String uid = userId != null ? userId.toString() : request.getSession().getId();
        //get cache for userId or sessionId
        String preRequestUrl = null;
        Object redisRequestTime;
        if (null != channel) {
            Object value = channel.get(requestCache, lastRequestUrl + uid, true).getValue();
            if (null != value) {
                preRequestUrl = value.toString();
            }
            redisRequestTime = channel.get(requestCache, lastRequestTime + uid, true).getValue();
        } else {
            Object attribute = request.getSession().getAttribute(lastRequestUrl + uid);
            if (null != attribute) {
                preRequestUrl = attribute.toString();
            }
            redisRequestTime = request.getSession().getAttribute(lastRequestTime + uid);
        }
        Long preRequestTime = 0L;
        if (null != redisRequestTime) {
            if (redisRequestTime instanceof String) {
                preRequestTime = Long.valueOf(redisRequestTime.toString());
            } else if (redisRequestTime instanceof Long) {
                preRequestTime = (Long) redisRequestTime;
            }
        }
        int seconds = minRequestIntervalTime;
        // 过滤频繁操作
        if (preRequestUrl != null) {
            long difference = System.currentTimeMillis() - preRequestTime;
            boolean isPreRequestTime = difference < minRequestIntervalTime;
            //如果这次的请求url为上次的请求url，或开启了拦截所有请求
            boolean isAllRequest = url.equals(preRequestUrl) || allRequest;
            //并且请求时间差小于设备的拦截时间差
            if (isAllRequest && isPreRequestTime) {
                logger.debug("malicious request interval time :{},min request interval time:{}", difference, minRequestIntervalTime);
                //非法请求次数
                Integer maliciousRequestTimes = null;
                if (null != channel) {
                    channel.get(requestCache, malicious + uid, true);
                } else {
                    Object attribute = request.getSession().getAttribute(malicious + uid);
                    if (null != attribute) {
                        maliciousRequestTimes = Integer.valueOf(attribute.toString());
                    }
                }
                if (maliciousRequestTimes == null) {
                    maliciousRequestTimes = 1;
                } else {
                    maliciousRequestTimes++;
                }
                if (maliciousRequestTimes > maxMaliciousNum) {
                    if (null != channel){
                        channel.set(requestCache, malicious + uid, 0,seconds,true);
                    }else {
                        request.getSession().setAttribute(malicious + uid, 0);
                    }
                    logger.warn("to intercept a malicious request : {}", url);
                    Map<String, Object> map = Maps.newHashMap();
                    map.put(RETURN_KEY_CODE, HttpCode.MULTI_STATUS.value());
                    map.put(RETURN_KEY_MSG, HttpCode.MULTI_STATUS.message());
                    map.put(RETURN_KEY_TIMESTAMP, System.currentTimeMillis());
                    logger.info("response : " + JSON.toJSON(map));
                    WebUtil.writer(response, map);
                    return false;
                }
                if (null != channel){
                    channel.set(requestCache, malicious + uid, maliciousRequestTimes,seconds,true);
                }else {
                    request.getSession().setAttribute(malicious + uid, maliciousRequestTimes);
                }
                //非法请求次数
            } else {
                if (null != channel){
                    channel.set(requestCache, malicious + uid, 0,seconds,true);
                }else {
                    request.getSession().setAttribute(malicious + uid, 0);
                }
            }
        }
        //from url or time save  cache
        if (null != channel){
            channel.set(requestCache, lastRequestUrl + uid, url,seconds,true);
            channel.set(requestCache, lastRequestTime + uid, System.currentTimeMillis(),seconds,true);
        }else {
            request.getSession().setAttribute(lastRequestUrl + uid, url);
            request.getSession().setAttribute(lastRequestTime + uid, System.currentTimeMillis());
        }
        return super.preHandle(request, response, handler);
    }

    /**
     * 判断是否是白名单
     */
    private boolean isWhiteReq(String requestUrl) {
        for (String urlTemp : whiteUrls) {
            if (requestUrl.contains(urlTemp.toLowerCase())) {
                return true;
            }
        }
        return false;
    }
}
